package wiki.xsx.core.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import wiki.xsx.core.config.redisson.RedissonConnectionFactory;

import java.time.Clock;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
 * 数据库助手
 * @author xsx
 * @date 2019/4/25
 * @since 1.8
 */
public class DBHandler{

    /**
     * 数据库信息选项枚举
     */
    enum DBOption {
        /**
         * Redis服务器的一般信息
         */
        SERVER("server"),
        /**
         * 客户端的连接部分相关信息
         */
        CLIENTS("clients"),
        /**
         * 内存消耗相关信息
         */
        MEMORY("memory"),
        /**
         * RDB和AOF相关信息
         */
        PERSISTENCE("persistence"),
        /**
         * 一般统计信息
         */
        STATS("stats"),
        /**
         * 主/从复制信息
         */
        REPLICATION("replication"),
        /**
         * CPU的相关信息
         */
        CPU("cpu"),
        /**
         * Redis命令统计相关信息
         */
        COMMANDSTATS("commandstats"),
        /**
         * Redis集群信息
         */
        CLUSTER("cluster"),
        /**
         * 数据库的相关统计信息
         */
        KEYSPACE("keyspace"),
        /**
         * 所有信息
         */
        ALL("all"),
        /**
         * 默认设置的信息
         */
        DEFAULT("default");

        /**
         * 选项
         */
        private String option;

        /**
         * 选项枚举构造
         * @param option 选项
         */
        DBOption(String option) {
            this.option = option;
        }
    }

    /**
     * 日志
     */
    private static final Logger log = LoggerFactory.getLogger(DBHandler.class);
    /**
     * 时钟
     */
    private static final Clock CLOCK = Clock.systemDefaultZone();
    /**
     * 对象模板
     */
    private RedisTemplate<String, Object> redisTemplate;
    /**
     * 字符串模板
     */
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 数据库助手构造
     * @param redisTemplate 对象模板
     * @param stringRedisTemplate 字符串模板
     */
    private DBHandler(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取实例
     * @param redisTemplate 对象模板
     * @param stringRedisTemplate 字符串模板
     * @return 返回实例
     */
    public static DBHandler getInstance(RedisTemplate<String, Object> redisTemplate, StringRedisTemplate stringRedisTemplate) {
        return new DBHandler(redisTemplate, stringRedisTemplate);
    }

    /**
     * 获取实例
     * @param dbIndex 数据库索引
     * @return 返回实例
     */
    public static DBHandler getInstance(int dbIndex) {
        List<RedisTemplate> templateList = HandlerManager.createTemplate(dbIndex);
        return getInstance(templateList.get(0), (StringRedisTemplate) templateList.get(1));
    }

    /**
     * 获取当前数据库索引
     * @since redis 1.0.0
     * @return 返回当前数据库索引
     */
    public int getDB() {
        int index;
        RedisConnectionFactory redisConnectionFactory = this.redisTemplate.getRequiredConnectionFactory();
        if (redisConnectionFactory instanceof LettuceConnectionFactory) {
            index = ((LettuceConnectionFactory)redisConnectionFactory).getDatabase();
        }else if (redisConnectionFactory instanceof JedisConnectionFactory) {
            index = ((JedisConnectionFactory)redisConnectionFactory).getDatabase();
        }else if (redisConnectionFactory instanceof RedissonConnectionFactory){
            index = ((RedissonConnectionFactory)redisConnectionFactory).getDatabase();
        }else {
            throw new RuntimeException("no support connection factory");
        }
        return index;
    }

    /**
     * 设置当前数据库索引
     * @see <a href="http://redis.io/commands/select">Redis Documentation: SELECT</a>
     * @since redis 1.0.0
     * @param dbIndex 数据库索引
     */
    @Deprecated
    public void setDB(int dbIndex) {
        if (this.redisTemplate.getRequiredConnectionFactory() instanceof RedissonConnectionFactory) {
            this.redisTemplate.setConnectionFactory(HandlerManager.getConnectionFactory(dbIndex));
            this.stringRedisTemplate.setConnectionFactory(HandlerManager.getConnectionFactory(dbIndex));
        }else {
            this.redisTemplate.getRequiredConnectionFactory().getConnection().select(dbIndex);
            this.stringRedisTemplate.getRequiredConnectionFactory().getConnection().select(dbIndex);
        }
    }

    /**
     * 获取原有数据库索引并设置新索引
     * @see <a href="http://redis.io/commands/select">Redis Documentation: SELECT</a>
     * @since redis 1.0.0
     * @param dbIndex 数据库索引
     * @return 返回原有索引
     */
    @Deprecated
    public int getAndSetDB(int dbIndex) {
        int index = getDB();
        setDB(dbIndex);
        return index;
    }

    /**
     * 检查连接
     * @see <a href="http://redis.io/commands/ping">Redis Documentation: PING</a>
     * @since redis 1.0.0
     * @return 返回延迟时间(ms)
     */
    public Long ping() {
        RedisConnection connection = this.redisTemplate.getRequiredConnectionFactory().getConnection();
        long begin = CLOCK.millis();
        connection.ping();
        long end = CLOCK.millis();
        return end - begin;
    }

    /**
     * 服务器时间
     * @see <a href="http://redis.io/commands/time">Redis Documentation: TIME</a>
     * @since redis 2.6.0
     * @return 返回服务器时间(ms)
     */
    public Long time() {
        return this.redisTemplate.getRequiredConnectionFactory().getConnection().serverCommands().time();
    }

    /**
     * 测试打印
     * @see <a href="http://redis.io/commands/echo">Redis Documentation: ECHO</a>
     * @since redis 1.0.0
     * @param message 信息
     * @return 返回打印信息
     */
    public String echo(String message) {
        RedisSerializer<String> serializer = RedisSerializer.string();
        return serializer.deserialize(
            this.redisTemplate
                .getRequiredConnectionFactory()
                .getConnection()
                .echo(serializer.serialize(message))
        );
    }

    /**
     * 清理当前数据库
     * @see <a href="http://redis.io/commands/flushdb">Redis Documentation: FLUSHDB</a>
     * @since redis 1.0.0
     */
    public void clearDB() {
        try {
            this.redisTemplate.getRequiredConnectionFactory().getConnection().serverCommands().flushDb();
        }catch (IllegalStateException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 清理所有数据库
     * @see <a href="http://redis.io/commands/flushall">Redis Documentation: FLUSHALL</a>
     * @since redis 1.0.0
     */
    public void clearDBAll() {
        try {
            this.redisTemplate.getRequiredConnectionFactory().getConnection().serverCommands().flushAll();
        }catch (IllegalStateException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 获取当前数据库信息
     * @see <a href="http://redis.io/commands/info">Redis Documentation: INFO</a>
     * @since redis 1.0.0
     * @return 返回当前数据库信息
     */
    public Properties getDBInfo() {
        return this.redisTemplate.getRequiredConnectionFactory().getConnection().serverCommands().info();
    }

    /**
     * 获取当前数据库信息
     * @see <a href="http://redis.io/commands/info">Redis Documentation: INFO</a>
     * @since redis 1.0.0
     * @param dbOption 选项
     * <p>SERVER: Redis服务器的一般信息</p>
     * <p>CLIENTS: 客户端的连接部分相关信息</p>
     * <p>MEMORY: 内存消耗相关信息</p>
     * <p>PERSISTENCE: RDB和AOF相关信息</p>
     * <p>STATS: 一般统计信息</p>
     * <p>REPLICATION: 主/从复制信息</p>
     * <p>CPU: CPU的相关信息</p>
     * <p>COMMANDSTATS: Redis命令统计相关信息</p>
     * <p>CLUSTER: Redis集群信息</p>
     * <p>KEYSPACE: 数据库的相关统计信息</p>
     * <p>ALL: 所有信息</p>
     * <p>DEFAULT: 默认设置的信息</p>
     * @return 返回当前数据库信息
     */
    public Properties getDBInfo(DBOption dbOption) {
        RedisServerCommands serverCommands = this.redisTemplate.getRequiredConnectionFactory().getConnection().serverCommands();
        if (dbOption!=null) {
            return serverCommands.info(dbOption.option);
        }
        return serverCommands.info(DBOption.DEFAULT.option);
    }

    /**
     * 设置数据库配置参数
     * @see <a href="http://redis.io/commands/config-set">Redis Documentation: CONFIG SET</a>
     * @since redis 2.0.0
     * @param param 参数名
     * @param value 参数值
     */
    public void setConfig(String param, String value) {
        this.redisTemplate.getRequiredConnectionFactory().getConnection().setConfig(param, value);
    }

    /**
     * 获取数据库配置信息
     * @see <a href="http://redis.io/commands/config-get">Redis Documentation: CONFIG GET</a>
     * @since redis 2.0.0
     * @param param 参数名
     */
    public Properties getConfig(String param) {
        return this.redisTemplate.getRequiredConnectionFactory().getConnection().getConfig(param);
    }

    /**
     * 重置配置状态
     * @see <a href="http://redis.io/commands/config-resetstat">Redis Documentation: CONFIG RESETSTAT</a>
     * @since redis 2.0.0
     */
    public void resetConfigStats() {
        this.redisTemplate.getRequiredConnectionFactory().getConnection().resetConfigStats();
    }

    /**
     * 设置对象客户端连接名称
     * @see <a href="http://redis.io/commands/client-setname">Redis Documentation: CLIENT SETNAME</a>
     * @since redis 2.6.9
     * @param name 名称
     */
    public void setClientNameAsObj(String name) {
        this.redisTemplate
            .getRequiredConnectionFactory()
            .getConnection()
            .setClientName(RedisSerializer.string().serialize(name));
    }

    /**
     * 设置字符串客户端连接名称
     * @see <a href="http://redis.io/commands/client-setname">Redis Documentation: CLIENT SETNAME</a>
     * @since redis 2.6.9
     * @param name 名称
     */
    public void setClientName(String name) {
        this.stringRedisTemplate
            .getRequiredConnectionFactory()
            .getConnection()
            .setClientName(RedisSerializer.string().serialize(name));
    }

    /**
     * 获取对象客户端连接名称
     * @see <a href="http://redis.io/commands/client-getname">Redis Documentation: CLIENT GETNAME</a>
     * @since redis 2.6.9
     */
    public String getClientNameAsObj() {
        return this.redisTemplate.getRequiredConnectionFactory().getConnection().getClientName();
    }

    /**
     * 获取字符串客户端连接名称
     * @see <a href="http://redis.io/commands/client-getname">Redis Documentation: CLIENT GETNAME</a>
     * @since redis 2.6.9
     */
    public String getClientName() {
        return this.stringRedisTemplate.getRequiredConnectionFactory().getConnection().getClientName();
    }

    /**
     * 标记一个对象事务块的开始
     * @since redis 1.2.0
     * @see <a href="http://redis.io/commands/multi">Redis Documentation: MULTI</a>
     */
    public void beginTransactionAsObj() {
        this.redisTemplate.multi();
    }

    /**
     * 标记一个字符串事务块的开始
     * @since redis 1.2.0
     * @see <a href="http://redis.io/commands/multi">Redis Documentation: MULTI</a>
     */
    public void beginTransaction() {
        this.stringRedisTemplate.multi();
    }

    /**
     * 提交所有对象事务块内的命令
     * @since redis 1.2.0
     * @see <a href="http://redis.io/commands/exec">Redis Documentation: EXEC</a>
     */
    public List commitAsObj() {
        return this.redisTemplate.exec();
    }

    /**
     * 提交所有字符串事务块内的命令
     * @since redis 1.2.0
     * @see <a href="http://redis.io/commands/exec">Redis Documentation: EXEC</a>
     */
    public List commit() {
        return this.stringRedisTemplate.exec();
    }

    /**
     * 取消对象事务
     * @see <a href="http://redis.io/commands/discard">Redis Documentation: DISCARD</a>
     * @since redis 2.0.0
     */
    public void cancelTransactionAsObj() {
        this.redisTemplate.discard();
    }

    /**
     * 取消字符串事务
     * @see <a href="http://redis.io/commands/discard">Redis Documentation: DISCARD</a>
     * @since redis 2.0.0
     */
    public void cancelTransaction() {
        this.stringRedisTemplate.discard();
    }

    /**
     * 监控对象
     * @see <a href="http://redis.io/commands/watch">Redis Documentation: WATCH</a>
     * @since redis 2.2.0
     * @param keys 键
     */
    public void watchAsObj(String ...keys) {
        this.redisTemplate.watch(Arrays.asList(keys));
    }

    /**
     * 监控字符串
     * @see <a href="http://redis.io/commands/watch">Redis Documentation: WATCH</a>
     * @since redis 2.2.0
     * @param keys 键
     */
    public void watch(String ...keys) {
        this.stringRedisTemplate.watch(Arrays.asList(keys));
    }

    /**
     * 取消监控对象
     * @see <a href="http://redis.io/commands/unwatch">Redis Documentation: UNWATCH</a>
     * @since redis 2.2.0
     */
    public void unwatchAsObj() {
        this.redisTemplate.unwatch();
    }

    /**
     * 取消监控字符串
     * @see <a href="http://redis.io/commands/unwatch">Redis Documentation: UNWATCH</a>
     * @since redis 2.2.0
     */
    public void unwatch() {
        this.stringRedisTemplate.unwatch();
    }

    /**
     * 获取spring redis模板
     * @return 返回对象模板
     */
    public RedisTemplate getRedisTemplate() {
        return this.redisTemplate;
    }

    /**
     * 获取spring string redis模板
     * @return 返回字符串模板
     */
    public StringRedisTemplate getStringRedisTemplate() {
        return this.stringRedisTemplate;
    }
}
